// Created by Windward Studios - no copyright is claimed. This code can be used in
// any manner by anyone for any reason. There is no copyright of any kind on it. You may
// use it in commercial products. You may change it without sharing those changes.
// We ask that you keep the "created by Windward Studios" in a comment at the top.

using System;
using System.IO;
using System.Net;
using System.Security.Principal;
using System.Text;
using System.Xml;
using System.Xml.XPath;
using net.windward.utils;

namespace Kailua.net.windward.utils.xml
{
	/// <summary>
	/// Provides the capability to open and test an xml file regardless of it's filename/url and the
	/// security mechanism. This includes most combinations where the calling program is on a domain
	/// or workgroup and the xml file is on a domain (same or different), workgroup, http, ftp, or a
	/// system with no uname/password required.
	/// It does not handle every combination because Windows does not provide the capabilities to do so
	/// in an API (even though "net use" can do so).
	/// </summary>
	public static class XmlFileOpen
	{

		/// <summary>
		/// What method used to connect.
		/// </summary>
		public enum CONNECT_MODE
		{
			/// <summary>
			/// Use windows identity.
			/// </summary>
			IDENTITY,
			/// <summary>
			/// Provide username and password.
			/// </summary>
			CREDENTIALS,
			/// <summary>
			/// REST protocol providing Accept and Content-Type.
			/// </summary>
			REST,
			/// <summary>
			/// Read from SharePoint.
			/// </summary>
			SHAREPOINT
		};

		/// <summary>
		/// Performs a XPathDocument.CreateNavigator using the passed in credentials.
		/// </summary>
		/// <param name="filename">The name of the file. Can be http(s) or ftp as well as a share or plain old filename.</param>
		/// <param name="xmlSettings">The settings used to read this XML file.</param>
		/// <param name="connectMode">The mode used for the connection.</param>
		/// <param name="username">Username to open under. If null then anonymous user.</param>
		/// <param name="password">Password to open under.</param>
		/// <returns>the loaded XML document.</returns>
		public static XPathNavigator XPathNavigatorCreate(string filename, XmlReaderSettings xmlSettings, CONNECT_MODE connectMode, string username, string password)
		{
			using (XmlReader reader = XmlReaderCreate(filename, xmlSettings, connectMode, username, password))
			{
				XPathDocument doc = new XPathDocument(reader);
				return doc.CreateNavigator();
			}
		}

		/// <summary>
		/// Open the XML document using the passed in credentials.
		/// </summary>
		/// <param name="filename">The name of the file. Can be http(s) or ftp as well as a share or plain old filename.</param>
		/// <param name="username">Username to open under. If null then anonymous user.</param>
		/// <param name="xmlSettings">The settings used to read this XML file.</param>
		/// <param name="connectMode">The mode used for the connection.</param>
		/// <param name="password">Password to open under.</param>
		/// <returns>the loaded XML document.</returns>
		public static XmlDocument XmlDocumentOpen(string filename, XmlReaderSettings xmlSettings, CONNECT_MODE connectMode, string username, string password)
		{
			Trap.trap();
			using (XmlReader reader = XmlReaderCreate(filename, xmlSettings, connectMode, username, password))
			{
				XmlDocument doc = new XmlDocument();
				doc.Load(reader);
				return doc;
			}
		}

		/// <summary>
		/// Performs a XmlReader.Create using the passed in credentials.
		/// </summary>
		/// <param name="filename">The name of the file. Can be http(s) or ftp as well as a share or plain old filename.</param>
		/// <param name="xmlSettings">The settings used to read this XML file.</param>
		/// <param name="connectMode">The mode used for the connection.</param>
		/// <param name="username">Username to open under. If null then anonymous user.</param>
		/// <param name="password">Password to open under.</param>
		/// <returns>the loaded XML document.</returns>
		public static XmlReader XmlReaderCreate(string filename, XmlReaderSettings xmlSettings,  CONNECT_MODE connectMode, string username, string password)
		{

			if (connectMode == CONNECT_MODE.IDENTITY)
			{
				XmlUrlResolver resolver = new XmlZipResolver();
				xmlSettings.XmlResolver = resolver;
				return XmlReader.Create(filename, xmlSettings);
			}

			if (connectMode == CONNECT_MODE.REST)
			{
				HttpWebRequest request = (HttpWebRequest) WebRequest.Create(filename);
				request.Accept = "application/xml";
				request.ContentType = "application/xml";
				request.KeepAlive = true;
				if (!string.IsNullOrEmpty(username))
				{
					// this part is not used until after a request is refused, but we add it anyways
					CredentialCache myCache = new CredentialCache();
					myCache.Add(new Uri(filename), "Basic", new NetworkCredential(username, password));
					request.Credentials = myCache;

					// this is how we put the uname/pw in the first request
					string cre = String.Format("{0}:{1}", username, password);
					byte[] bytes = Encoding.ASCII.GetBytes(cre);
					string base64 = Convert.ToBase64String(bytes);
					request.Headers.Add("Authorization", "Basic " + base64);
				}

				HttpWebResponse response = (HttpWebResponse) request.GetResponse();
				return XmlReader.Create(response.GetResponseStream(), xmlSettings);
			}

			if (connectMode == CONNECT_MODE.SHAREPOINT)
			{
				Trap.trap();
				byte[] fileContents = FileUtils.ReadSharePointFile(filename, username, password);

				XmlUrlResolver resolver = new XmlZipResolver();
				xmlSettings.XmlResolver = resolver;
				MemoryStream buf = new MemoryStream(fileContents);
				return XmlReader.Create(buf, xmlSettings);
			}

			// get the username & domain
			string domain, user;
			int pos = username.IndexOf('\\');
			if (pos == -1)
				pos = username.IndexOf('/');
			bool hasDomain;
			if (pos != -1)
			{
				hasDomain = true;
				domain = username.Substring(0, pos);
				user = username.Substring(pos + 1);
			}
			else
			{
				hasDomain = false;
				WindowsIdentity ident = WindowsIdentity.GetCurrent();
				if (ident == null)
					domain = null;
				else
				{
					domain = ident.Name;
					pos = domain.IndexOf('\\');
					if (pos != -1)
						domain = domain.Substring(0, pos);
				}
				user = username;
			}

			// CONNECT_MODE.CREDENTIALS - try the various ways to pass credentials
			// 1. Use standard XmlReader. We have to try and catch the exception because it has no way to gently tell us it couldn't do it.
			try
			{
				XmlUrlResolver resolver = new XmlZipResolver();
				resolver.Credentials = hasDomain ? new NetworkCredential(user, password, domain) : new NetworkCredential(user, password);
				xmlSettings.XmlResolver = resolver;
				return XmlReader.Create(filename, xmlSettings);
			}
			catch (Exception)
			{
				xmlSettings.XmlResolver = null;
			}


			// 2. we try a domain set of credentials
			if (domain != null)
				try
				{
					XmlReader doc = NativeMethods.OpenAsUser(filename, xmlSettings, domain, user, password);
					if (doc != null)
						return doc;
					if (hasDomain)
						throw new IOException("Could not open: " + filename + " as user: " + domain + "\\" + user);
				}
				catch (Exception)
				{
					if (hasDomain)
						throw;
				}

			// 3. we try as a local user on this system
			return NativeMethods.OpenAsUser(filename, xmlSettings, ".", user, password);
		}
	}
}